// 参考  http://www.a4z.cn/fe/2019/03/13/vue-template-compler-2/ 
// 动态属性值（如包今 @、:、v- 等等 Vue 专有的）的匹配
const unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/;
const dynamicArgAttribute = /^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // 匹配属性
const ncname = "[a-zA-Z_][\\-\\.0-9_a-zA-Z" + (unicodeRegExp.source) + "]*"; // aaa-bb aaa.bb aa3.b
const qnameCapture = "((?:" + ncname + "\\:)?" + ncname + ")";  // <aaa:bbbkk>
const startTagOpen = new RegExp(("^<" + qnameCapture)); // 匹配开头标签的<div
const startTagClose = /^\s*(\/?)>/; // 匹配结束标签的 >
const endTag = new RegExp(("^<\\/" + qnameCapture + "[^>]*>")); // 匹配结尾的标签 </div>
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g; // 匹配 {{}}
const comment = /^<!\--/; // 匹配注释



const ELEMENT_TYPE = 1; // 表示元素节点
const TEXT_TYPE = 3; // 表示文本节点

/**
 * 把模板分割对应的标签对应的属性生成 AST, 就是创建 AST 语法树结构
 * @param {*} tagName 表签名 比如 div span p 等等
 * @param {*} attrs 标签对应的属性 比如 id classs
 * @returns 返回 AST 结构
 */
function createASTElement(tagName, attrs) {
  return {
    tag: tagName, // 标签名
    attrs, // 标签属性
    children: [], // 子元素
    parent: null, // 父节点标签
    type: ELEMENT_TYPE, // 节点类型
  }
}


export function parseHTMLToAST(html) {

  let astRoot = null; // 存储木板中标签元素的树形结构信息

  // 匹配一个标签放一个标签到栈中 [div, p, span]， 遇到结束标签就把结束标签与栈中最后一个元素比较
  // <div><p><span></span></p></div>
  const stack = [];

  const start = function (tagName, attrs) {
    // console.log('开始标签', tagName, attrs)
    // 遇到开始标签，就创建 AST
    const element = createASTElement(tagName, attrs);
    if (astRoot == null) {
      // 如果没有根元素，当前元素就赋值给根元素
      astRoot = element;
    }

    const parent = stack[stack.length - 1]; // 获取栈中最后一个元素(当前元素)
    if (parent) {
      element.parent = parent; // 自己记住父亲是谁
      parent.children.push(element); // 父亲记住儿子是谁
    }

    // 存放开始标签元素到栈中
    stack.push(element);
  }
  const end = function (tagName) {
    // console.log('结束标签', tagName)
    stack.pop();
  }
  const chars = function (text) {
    // console.log('文本内容', text)
    text = text.replace(/\s/g, ''); // 空格
    if (text) {
      // 匹配结束标签时，需要取出栈中的最后一个元素是否与结束标签一致
      const parent = stack[stack.length - 1]; // 获取栈中最后一个元素
      parent.children.push({
        text,
        type: TEXT_TYPE
      });
    }
  }

  const advance = function (n) {
    html = html.substring(n); // 截取
  }

  // 匹配开始标签<div id="app" class="xxx" disabled a=xxx>
  const parseStartTag = function () {
    // 判断当前标签是否开始标签
    const matchs = html.match(startTagOpen); // ["<div","div"]
    if (matchs) {
      // 存储当前标签的信息
      const match = {
        tagName: matchs[1], // 标签名
        attrs: [] // 标签的属性
      };
      advance(matchs[0].length); // 匹配到开始标签属，去掉开始标签

      let end, attr;
      // 不是开始 < 符号就循环，直到匹配到结束 > 符号为止
      while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
        match.attrs.push({
          name: attr[1],
          value: attr[3] || attr[4] || attr[5] || true
        });
        advance(attr[0].length); // 匹配到标签属性，就删掉属性
      }
      if (end) {
        advance(end[0].length); // 匹配结束 > 标签属性，就删掉属性
        return match;
      }
    }
  }

  while (html) {
    const textEnd = html.indexOf('<');
    // 是否是标签的开始位置 <
    if (textEnd === 0) {
      const startTagMatch = parseStartTag();
      if (startTagMatch) {
        start(startTagMatch.tagName, startTagMatch.attrs);
        // 如果开始标签匹配完了，继续下一次的匹配
        continue;
      }
      const endTagMatch = html.match(endTag);
      if (endTagMatch) {
        end(endTagMatch[1]);
        advance(endTagMatch[0].length);
        // 当前结束标签匹配完，继续下一次匹配
        continue;
      }
    }
    let text;
    if (textEnd >= 0) {
      text = html.substring(0, textEnd);
    }
    // 如果有文本
    if (text) {
      // 去掉文本两端的空格
      advance(text.length);
      chars(text);
    }
  }
  // console.log('AST', astRoot)
  // 返回 AST 树
  return astRoot;
}